These applications demonstrate how to write a background task which uses Apple events to pass on task related information to another application.
The 'Folder Watcher FBA' application runs in the background with no user interface. This type of application is known as a 'Faceless Background Application' (FBA), a 'Background Only Application' (BOA), or a 'Daemon'. The 'Folder Watcher FBA'
watches folders for changes, if there is a change it sends an Apple event to the 'FW Receiver' which notifies the user through the Notification Manager and keeps a list of file/folder changes.
The 'Folder Watcher Script App' is supplied as a scripting alternative to writing an FBA. 'Folder Watcher Script App' is a AppleScript script application which notifies the 'FW Receiver' of changes by sending AppleScript subroutine Apple events.
The following is a list of features that these applications demonstrate:
• How to write a Faceless Background Application that performs a task.
• Sending and receiving application specific Apple events.
• How to receive AppleScript subroutine Apple events.
• Use of the Notification Manager to notify users using alerts, flashing menu bar icon and sound.
• PBCatSearch() of files between modification dates.
• How to write a script application that runs in idle time.
How to use these Applications
Folder Watcher FBA
To watch a folder simply drag the folder onto the application. If the folder is not already being watched the 'Folder Watcher FBA' will add the folders path to its resources. When you relaunch the program these resources are used to initialize the folders watched. To remove a folder from being watched you need to change the path name, for example by changing name of the folder, and relaunch the 'Folder Watcher FBA'. This will remove the resource that stores the path to the folder. Alternatively you can open 'Folder Watcher FBA' with a resource editor like ResEdit and delete the path which is stored as a 'WFol' resource.
The FBA has no interface so I have included the script 'Quit Folder Watcher FBA' which you can run to quit the 'Folder Watcher FBA'.
There is a resource that tells the 'Folder Watcher FBA' approximately how long it should take to check all the folders it is watching. This time is in seconds and stored in the 'Cycl' resource. You can also change the target application creator type for which the events are sent. To do this alter the 'Targ' resource to the four letter OSType.
FW Receiver
'FW Receiver' is the application which receives notification of folder changes via Apple events. When 'FW Receiver' receives an event telling it that there's been a modification it displays the file/folder modified, the type of change, the time and date in a list, and uses the Notification Manager to flash an icon in the menu bar. The icon will continue to flash until the application is suspended or resumed. The 'FW Receiver' will also play a sound when there is a modification.
Note - If this application is not running then you may think that the folder watching applications aren't working because they will only send the change events if the 'FW Receiver' is a current process.
Folder Watcher Script App
This is provided as a scripting alternative to 'Folder Watcher FBA'. It's not as comprehensive as 'Folder Watcher FBA' because it doesn't recognize changes to files within watched folders. Folders may be dragged onto it like the 'Folder Watcher FBA' and it stores these folders in a property. Properties keep their values between application launches, so to clear the folders watched by the script application you need to open it with a script editor e.g. Script Editor. Once opened 'dirty' the script by making an insignificant change, this forces a compile which clears the folder list back to its initial empty state, then save.
You can alter the time the 'Folder Watcher Script App' takes to check all watched folders by setting the cycleTime property in the script. To alter the target application to receive the change events set the targetAppCreator property to the applications creator type.
Building the Applications
'Folder Watcher FBA' and 'FW Receiver' have been built under:
Metrowerks CodeWarrior 7 and CodeWarrior 8
Symantec C++ 8.0.1
Symantec 7.0.4
MPW E.T.O. #19- 'Latest MPW': Symantec C++ for MPW and MrC.
The Symantec environments are using a slightly older version of the Universal Interfaces than MPW and CodeWarrior. Due to the source code using some of the new accessor routines you will need to use a later version of the Universal Interfaces than is provided with the Symantec products, or make some changes to the source code. To change the Universal Interfaces, simply place brackets around the existing folder and place the folder containing the later version into the same folder as the existing ones. The brackets will prevent the development environment from using the files contained within the old folder.
'Folder Watcher Script App' was simply compiled using the Script Editor.
Implementation
Folder Watcher FBA
Faceless Background Applications behave exactly the same as a normal Mac application, except they do not display windows or a menu bar and are not listed in the Application menu. An FBA is recognized as such by having the backgroundOnly bit set in its 'SIZE' -1 resource. You should also have the canBackground bit set. If you do not have these flags set your application will probably crash.
The only Toolbox initialization call you need to make is InitGraf() which initializes the QuickDraw globals. Seeing as an FBA has no interface, calls to InitWindows() and other interface Managers shouldn't be made.
An FBA has only a 2K stack by default, if your FBA needs more stack space then you can use the GetApplLimit() and SetApplLimit() toolbox routines to increase it. This should be done before any other initialization. The 'Folder Watcher FBA' does this with the following calls in main():
// set the stack size
numBytes = StackSize * 1024;
// calculate the absolute memory byte by
// offsetting the stack base
newLimit = (long)LMGetCurStackBase( ) - numBytes;
// check that there is enough memory for stack increase
if ((long)GetApplLimit( ) > newLimit)
SetApplLimit( (Ptr)newLimit );
Due to an FBA not showing up in the list of applications running, a user may not even know an FBA is running. If your FBA is running and is 'greedy' with the time it takes between WaitNextEvent() calls, the user may experience stuttering in the application they are currently working in. Take for example a user working in a word processing application, if your FBA is 'greedy' then the user may type in a word but have a delay before it appears on screen, or even lose some of the characters typed. This gives the user a less pleasant computing experience and that should be reserved for other operating systems.
Asynchronous File Manager calls are used in the 'Folder Watcher FBA' for task related disk accesses. The asynchronous calls return control to the application straight away. Unfortunately the current system won't context switch out of an application until all asynchronous File Manager calls are complete. That means that unless you use the asynchronous time between making the File Manager call and calling WaitNextEvent() in your own application, other applications won't get time until the calls are complete. Hence you currently don't want to make lengthy File Manager calls in an FBA. If you're interested in more information on asynchronous calls then you should look at the 'Asynchronous Routines on the Macintosh' article by Jim Luther in develop and the follow up 'More on Asynchronous Routines in Issue 13', see the references.
Obviously the 'Folder Watcher FBA' is doing the folder watching task in stages e.g. calling PBCatSearch() then checking the result the next time we get processing time. Breaking up a task into smaller parts helps to reduce the time the FBA takes between WaitNextEvent() calls. Something to watch out for in this approach is quitting the FBA while the task is in a critical stage e.g. waiting for an asynchronous call that puts results into memory allocated by the application.
An FBA will automatically be woken up when it receives a high level event. Consequently, if you do not need periodic null events you should call WaitNextEvent() with as large a sleep value as possible. If you do need periodic null events, like the 'Folder Watcher FBA', you still want to keep the sleep time as long as possible. The time your application sleeps compared to the value passed to WaitNextEvent() is only approximate. If there are no other applications wanting time then your application may get processing time alot earlier. So, if your FBA only needs to do its task at a given time you should keep track of the time elapsed yourself. The 'Folder Watcher FBA' does this in the DoSearchIdle() routine in 'FBATask.c', returning an updated sleep time.
If your FBA needs to alert the user you can either pass a message onto another application e.g. 'FW Receiver', or use the Notification Manager. Remember that your FBA has no interface so you cannot flash an icon or mark your application in the Application menu. There is a routine called Notify() in 'FBA.c' which uses the Notification Manager to display an alert.
The 'Folder Watcher FBA' basically keeps two lists. The first is a list of folders to watch, the second a list of volumes which the folders are on. The cycle time is divided by the number of volumes and this is the basic amount of time I pass to WaitNextEvent() for sleep time. When it comes to checking the watch folders I use a PBCatSearch() to search the whole volume for file changes between the last modification date check and the current time. The results are checked through, if an FSSpec returned is one of the folders being watched we know that files have been added and/or removed from the folder. A PBGetCatInfo() is called to find out the number of files in the directory, the difference between this number and the number stored determines the type of Apple event sent (see Limitations and Known Problems below). If the resulting FSSpec from the PBCatSearch() is a file then the parent ID of the folder it is in, is checked against all the watched folders. If it is being watched then a modification Apple event is sent.
The main reason I used PBCatSearch(), instead of calling PBGetCatInfo() on each watched folder like the 'Folder Watcher Script App' does, is that you get all the file modifications as well - almost like getting something for free. Also, if all you need to know of a watched folder is modification rather than whether files are added or removed, which is only a guideline anyway, you could remove the PBGetCatInfo() calls. Then the time used by the FBA would remain fairly constant how ever many folders you watch on a volume. The only problem is in the time it takes to complete a search. It is fast, but may need to have search time limits put on so users don't notice it running in the background.
FW Receiver
This application is basically the 'Tabs LDEF Demo' by Chris White with support for accepting the Apple events from both the 'Folder Watcher FBA' and the 'Folder Watcher Script App'. While the 'Folder Watcher FBA' sends application defined Apple events, the 'Folder Watcher Script App' sends AppleScript subroutine events. One reason for this is because the 'FW Receiver' does not have an 'aete' resource, this resource defines the applications AppleScript terminology. The second reason is to actually show how an application can accept an event from AppleScript when AppleScript has no 'aete' resource to compile a script with. It is not a recommended way to receive events from AppleScript scripts because, for one, it does not allow a script to be converted so nicely to another language. It also limits syntax checking of a script. However, under certain circumstances this technique may be useful, for example if the target application, with its 'aete' resource, is unavailable when compiling a script. This may be due to use of a variable application name in a script.
To accept an AppleScript subroutine event you must install an Apple event handler for the event class 'kASAppleScriptSuite', and the event ID of 'kASSubroutineEvent', these are defined in 'ASRegistry.h'. When you receive an event there is a parameter specified by the keyword 'keyASSubroutineName' which is of typeChar and holds the name of the subroutine. The direct object contains the parameters for the routine. HandleASSubroutine() in 'AppleEvents.c' shows how this event can be handled.
When a change event is received it notifies the user through the Notification Manager by flashing an icon and playing a sound. Inside Macintosh says that the nmIcon field of an NMRec should contain a handle to a small icon which will blink periodically in the menu bar. The handle can actually be a handle to a small icon or an icon suite. The InstallNotification() routine in the project source file 'AppleEvents.c' uses an icon suite so that it doesn't need to worry about the monitor depth. The technical note 'QD18-Drwng Icons the Syst 7 Way' contains more information on icon suites, see the references. There should be preferences available which allow the user to turn on and off the way the application notifies them. This is not implemented in 'FW Receiver'.
Folder Watcher Script App
This script application revolves around a list of records. Each record contains an alias to the folder to watch, along with the last modification date and how many files were in the folder. The application divides up the cycleTime property so that it checks each folder within this time. Each time the idle handler is called it checks one folder sending an Apple event if there is a change in the modification date.
The 'Folder Watcher Script App' could use the Scriptable Finder to get a list of recently modified files, however the current version of the Scriptable Finder is too slow to be able to perform this in the background.
Limitations and Known Problems
There are limitations to watching a folder. Most folder watching applications simply watch a folders modification date to see if a file is added to it. Then when a file is added they move the file to another folder and do further processing on it e.g. the Print Monitor. The problem with watching for changes within a folder periodically is that a combination of changes can be made between checks that could lead the folder watcher to come to the wrong conclusions.
Take for example the changes that would occur within a folder if the user removed a file, then added another file and renamed the new file to the same name as the file just removed. In this case only keeping a record of the number of files in a watched folder is not enough information to understand what happened. Even keeping a list of files within the folder would cause problems if you only referred to them by their names. 'FL31-Searching Volumes' is a good technical note that discusses related issues, see references for where you can find this.
When watching a folder on a server you have to keep in mind that modifications made to a file on a server have a modification date determined by the clock on the server. This means that if the clock on the 'Folder Watcher FBA' machine is different from the server machines clock you can miss some modifications. Besides this, watching a folder over a network may result in bad performance due to the PBCatSearch(). Of course the 'Folder Watcher Script App' will not be affected by this because it is based off the last modification date on each folder.
Due to the file representing a folder getting modified if it is moved into a another folder. If you move a folder into a watched folder then you will get an 'Added' change for the watched folder and a 'Modified' change for the actual folder moved in.
Future Changes
These applications are here as samples, and do not necessarily show the best way of doing things. That is dependent on your application and what it does. With the limits of time, some features that may be useful or better in these applications have been left out. Here are some things that could be done which would improve the applications:
• Break up the search time in PBCatSearch() and call repeatedly until search is complete.
• Store the folders to be watched by the 'Folder Watcher FBA' in aliases rather than as paths.
• Include the ability to watch volumes.
• Fully script the 'FW Receiver' by giving it a terminology ('aete') resource. This would allow AppleScript scripts to compile with application defined commands instead of sending AppleScript subroutine events.
• Send the files changed in an Apple event list, rather than a single file change at a time.
• Compile a list of subfolders contained in each watched folder. Then compare modified files returned from PBCatSearch() to these folders also. This would give a folder watcher that can watch a folder and all of its subfolders.
Finally
Please do not take the sounds used in this application to be good examples of using sound. The same may be said for the icons, which by the way, actually represent a shepherd and a dog watching over the flock of folders. Okay, so this may be pushing the folder watching idea a little too far, but I needed to show the use of an icon suite with the Notification Manager…
Thanks to Jason Hodges-Harris for editing the sounds, and Scott Grahams for ideas on one of the icons.
'Tabs LDEF Demo' can be found on this Developer CD.
'Inside Macintosh - Processes' Chapter 5 'Notification Manager'
'QD18-Drwng Icons the Syst 7 Way' is available on the latest Reference Library Developer CD. From the 'Technical Documentation' folder the path is:
'Technical Documentation:Macintosh Technical Notes:QuickDraw:QD18-Drwng Icons the Syst 7 Way'
'FL31-Searching Volumes' this technical note may be found on the latest Reference Library Developer CD. From the 'Technical Documentation' folder the path is: